﻿using System;
using System.Collections;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text.RegularExpressions;

namespace DealPLC
{
    /// <summary>
    /// 这个注释保留
    /// Modbus TCP common driver class. This class implements a modbus TCP master driver.
    /// It supports the following commands:
    /// 
    /// Read coils -useless
    /// Read discrete inputs -useless
    /// Write single coil -useless
    /// Write multiple cooils -useless
    /// Read holding register
    /// Read input register
    /// Write single register
    /// Write multiple register
    ///                                             -useless
    /// All commands can be sent in synchronous or asynchronous mode. If a value is accessed
    /// in synchronous mode the program will stop and wait for slave to response. If the 
    /// slave didn't answer within a specified time a timeout exception is called.
    /// The class uses multi threading for both synchronous and asynchronous access. For
    /// the communication two lines are created. This is necessary because the synchronous
    /// thread has to wait for a previous command to finish.
    /// 
    /// </summary>
    public partial class ModBusTCP
    {
        #region ModBusTCP function 定义

        #region Function
        private const byte fctReadCoil = 1;
        private const byte fctReadDiscreteInputs = 2;
        private const byte fctReadHoldingRegister = 3;
        private const byte fctReadInputRegister = 4;
        private const byte fctWriteSingleCoil = 5;
        private const byte fctWriteSingleRegister = 6;
        private const byte fctWriteMultipleCoils = 15;
        private const byte fctWriteMultipleRegister = 16;
        private const byte fctReadWriteMultipleRegister = 23;
        #endregion

        #region 异常参数
        /// <summary>function参数非法</summary>
        public const byte excIllegalFunction = 1;

        /// <summary>data address参数非法</summary>
        public const byte excIllegalDataAdr = 2;

        /// <summary>data value参数非法.</summary>
        public const byte excIllegalDataVal = 3;

        /// <summary>slave device failure从动设备异常.</summary>
        public const byte excSlaveDeviceFailure = 4;

        /// <summary>acknowledge答复异常.</summary>
        public const byte excAck = 5;

        /// <summary> slave is busy/booting up 从动设备繁忙.</summary>
        public const byte excSlaveIsBusy = 6;

        /// <summary>Constant for exception gate path unavailable.</summary>
        public const byte excGatePathUnavailable = 10;

        /// <summary>not connected 连接断开/未连接</summary>
        public const byte excExceptionNotConnected = 253;

        /// <summary>connection lost 连接断开</summary>
        public const byte excExceptionConnectionLost = 254;

        /// <summary>response timeout 等待答复超时.</summary>
        public const byte excExceptionTimeout = 255;

        /// <summary>offset 参数错误.</summary>
        private const byte excExceptionOffset = 128;

        /// <summary>send failt 发送失败</summary>
        private const byte excSendFailt = 100;
        #endregion

        #region 通讯定义
        /// <summary>
        /// socket端口 异步
        /// </summary>
        private Socket tcpAsyCl;
        private byte[] tcpAsyClBuffer = new byte[2048];
        /// <summary>
        /// socket端口 同步
        /// </summary>
        private Socket tcpSynCl;
        private byte[] tcpSynClBuffer = new byte[2048];

        /// <summary>Response data event. This event is called when new data arrives</summary>
        public delegate void ResponseData(ushort id, byte unit, byte function, byte[] data);
        /// <summary>Response data event. This event is called when new data arrives</summary>
        public event ResponseData OnResponseData;
        /// <summary>Exception data event. This event is called when the data is incorrect</summary>
        public delegate void ExceptionData(ushort id, byte unit, byte function, byte exception);
        /// <summary>Exception data event. This event is called when the data is incorrect</summary>
        public event ExceptionData OnException;
        #endregion

        #region 其他参数定义
        private static ushort _timeout = 500;
        /// <summary>答复超时的设定</summary>
        /// <value>The default value is 500ms.</value>
        public ushort Timeout
        {
            get
            {
                return _timeout;
            }
            set
            {
                _timeout = value;
            }
        }

        private static ushort _refreshTime = 10;
        /// <summary>从动设备答复的刷新时间</summary>
        /// <value>The default value is 10ms.</value>
        public ushort RefreshTime
        {
            get
            {
                return _refreshTime;
            }
            set
            {
                _refreshTime = value;
            }
        }

        private static bool _connected = false;
        /// <summary>连接状态</summary>
        public bool Connected
        {
            get { return _connected; }
        }
        #endregion

        #endregion

        #region 初始化
        /// <summary>无参构造函数</summary>
        public ModBusTCP()
        {
        }

        /// <summary>含参构造函数</summary>>
        public ModBusTCP(string ip, ushort port, out string strError)
        {
            Connect(ip, port, out strError);
        }

        /// <summary>
        /// 连接从站
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口</param>
        public void Connect(string ip, ushort port, out string strError)
        {
            try
            {
                strError = "";
                IPAddress _ip;
                if (IPAddress.TryParse(ip, out _ip) == false)
                {
                    IPHostEntry hst = Dns.GetHostEntry(ip);
                    ip = hst.AddressList[0].ToString();
                }

                #region 异步 暂不使用
                //异步的功能暂时不适用
                // Connect asynchronous client
                //tcpAsyCl = new Socket(IPAddress.Parse(ip).AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                //tcpAsyCl.Connect(new IPEndPoint(IPAddress.Parse(ip), port));
                //tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout);
                //tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout);
                //tcpAsyCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);

                #endregion

                // 同步socket的初始化和使用
                tcpSynCl = new Socket(IPAddress.Parse(ip).AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                tcpSynCl.Connect(new IPEndPoint(IPAddress.Parse(ip), port));
                //设置一些基础属性
                tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout);
                tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout);
                tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
                _connected = true;
            }
            catch (Exception ex)
            {
                strError = ex.Message;
                _connected = false;
            }
        }

        /// <summary>断开连接</summary>
        public void Disconnect()
        {
            Dispose();
        }

        /// <summary>析构函数</summary>
        ~ModBusTCP()
        {
            Dispose();
        }

        /// <summary>Destroy master instance</summary>
        public void Dispose()
        {
            if (tcpAsyCl != null)
            {
                if (tcpAsyCl.Connected)
                {
                    try { tcpAsyCl.Shutdown(SocketShutdown.Both); }
                    catch { }
                    tcpAsyCl.Close();
                }
                tcpAsyCl = null;
            }
            if (tcpSynCl != null)
            {
                if (tcpSynCl.Connected)
                {
                    try { tcpSynCl.Shutdown(SocketShutdown.Both); }
                    catch { }
                    tcpSynCl.Close();
                }
                tcpSynCl = null;
            }
        }

        /// <summary>
        /// 异常处理
        /// </summary>
        /// <param name="id"></param>
        /// <param name="unit"></param>
        /// <param name="function"></param>
        /// <param name="exception"></param>
        internal void CallException(ushort id, byte unit, byte function, byte exception)
        {
            if (tcpAsyCl == null) return;

            if (exception == excExceptionConnectionLost)
            {
                tcpSynCl = null;
                tcpAsyCl = null;
            }

            OnException?.Invoke(id, unit, function, exception);
        }

        #endregion

        #region 读取寄存器
        /// <summary>
        /// 读取单个16位寄存器的值
        /// </summary>
        /// <param name="regName">首个寄存器名称</param>
        /// <returns></returns>
        public bool ReadSingleReg(string regName, ref int value)
        {
            byte[] bitValues = ReadBitData(regName);
            if (bitValues == null || bitValues.Length == 0)
            {
                return false;
            }
            //高低位转换后，在转换为16位有符号数据
            value = BitConverter.ToInt16(ConvertBetweenPanData(bitValues), 0);
            return true;
        }

        /// <summary>
        /// 读取多个16位寄存器的值，默认带符号
        /// </summary>
        /// <param name="regName">首个寄存器名称</param>
        /// <param name="length">寄存器个数（16位）</param>
        /// <returns></returns>
        public bool ReadMultReg(string strRegName, int length, ref int[] value)
        {
            value = new int[length];
            byte[] bitValuesConverted;
            byte[] bitValues;
            try
            {
                bitValues = ReadBitData(strRegName, length);
                if (bitValues == null || bitValues.Length == 0)
                {
                    return false;
                }
                //高低位变换
                bitValuesConverted = ConvertBetweenPanData(bitValues);
                //转换为16为有符号数据
                for (int i = 0; i < length; i++)
                {
                    value[i] = BitConverter.ToInt16(bitValuesConverted, i * 2);
                }
            }
            //捕捉到的异常抛出到上层做记录
            catch (Exception)
            {
                throw;
            }

            //返回16位的带符号整数
            return true;
        }

        /// <summary>
        /// 读取基础的bit数据
        /// </summary>
        /// <param name="strRegName"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        private byte[] ReadBitData(string strRegName, int length = 1)
        {
            byte[] bitValues = new byte[] { };
            try
            {
                //ID位，读取00开头，单个101，多个102
                ushort id = (ushort)(100 + (length > 0 ? 1 : 2));
                //开始地址的数组部分
                int intStartAdr = ReadStartAdr(strRegName);
                ushort startAdr = Convert.ToUInt16(intStartAdr);
                //modbus标识
                byte unit = startAdr > 255 ? (byte)254 : Convert.ToByte(intStartAdr);
                //生成modbus头数据
                byte[] temp = CreateReadHeader(id, unit, startAdr, (ushort)length, fctReadHoldingRegister);
                WriteSyncData(temp, id, ref bitValues);
            }
            catch (Exception)
            {
                throw;
            }
            return bitValues;
        }

        /// <summary>
        /// Create modbus header for read action
        /// 为读Modbus创建头数据
        /// </summary>
        private byte[] CreateReadHeader(ushort id, byte unit, ushort startAddress, ushort length, byte function)
        {
            byte[] data = new byte[12];

            byte[] _id = BitConverter.GetBytes((short)id);
            data[0] = _id[1];			    // Slave id high byte
            data[1] = _id[0];				// Slave id low byte
            data[5] = 6;					// Message size
            data[6] = unit;					// Slave address
            data[7] = function;				// Function code
            byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress));
            data[8] = _adr[0];				// Start address
            data[9] = _adr[1];				// Start address
            byte[] _length = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)length));
            data[10] = _length[0];			// Number of data to read
            data[11] = _length[1];			// Number of data to read
            return data;
        }
        #endregion

        #region 写入寄存器
        /// <summary>Write single register in slave synchronous.</summary>
        /// <param name="strRegName">首位寄存器名</param>
        public bool WriteSingleReg(string strRegName, int value)
        {
            byte[] bitData = BitConverter.GetBytes(value);
            byte[] bitConverted = ConvertBetweenPanData(bitData);
            WriteBitData(strRegName, bitConverted);
            return true;
        }

        /// <summary>Write multiple registers in slave synchronous.</summary>
        /// <param name="id">Unique id that marks the transaction. In asynchonous mode this id is given to the callback function.</param>
        /// <param name="unit">Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function.</param>
        /// <param name="startAddress">Address to where the data is written.</param>
        /// <param name="values">Contains the register information.</param>
        /// <param name="result">Contains the result of the synchronous write.</param>
        public bool WriteMultReg(string strRegName, int[] values)
        {
            byte[] bitData = new byte[values.Length * 2];
            for (int i = 0; i < values.Length; i++)
            {
                Array.Copy(BitConverter.GetBytes(values[i]), 0, bitData, 2 * i, 2);
            }
            //高低位交换
            byte[] bitConverted = ConvertBetweenPanData(bitData);

            return WriteBitData(strRegName, bitConverted);
        }

        private bool WriteBitData(string strRegName, byte[] bitValues)
        {
            //写入的寄存器的个数
            int length = bitValues.Length / 2;
            int intStartAdr = ReadStartAdr(strRegName);
            ushort id = (ushort)(200 + (1 > 0 ? 1 : 2));
            byte unit = intStartAdr > 255 ? (byte)254 : Convert.ToByte(intStartAdr);
            ushort startAdr = Convert.ToUInt16(intStartAdr);
            //写入指令的头数据
            byte[] data = CreateWriteHeader(id, unit, startAdr, (ushort)length, (ushort)(bitValues.Length + 2), fctWriteMultipleRegister);
            //从第13开始为写入的数据的值
            Array.Copy(bitValues, 0, data, 13, length * 2);
            byte[] bitValue = new byte[] { };
            return WriteSyncData(data, id, ref bitValue);
        }

        /// <summary>
        /// Create modbus header for read action
        /// 为写Modbus创建头数据
        /// </summary>
        private byte[] CreateWriteHeader(ushort id, byte unit, ushort startAddress, ushort numData, ushort numBytes, byte function)
        {
            byte[] data = new byte[numBytes + 11];

            byte[] _id = BitConverter.GetBytes((short)id);
            data[0] = _id[1];				// Slave id high byte
            data[1] = _id[0];				// Slave id low byte
            byte[] _size = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(5 + numBytes)));
            data[4] = _size[0];				// Complete message size in bytes
            data[5] = _size[1];				// Complete message size in bytes
            data[6] = unit;					// Slave address
            data[7] = function;				// Function code
            byte[] _adr = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)startAddress));
            data[8] = _adr[0];				// Start address
            data[9] = _adr[1];				// Start address
            if (function >= fctWriteMultipleCoils)
            {
                byte[] _cnt = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)numData));
                data[10] = _cnt[0];			// Number of bytes
                data[11] = _cnt[1];			// Number of bytes
                data[12] = (byte)(numBytes - 2);
            }
            return data;
        }
        #endregion

        #region 基础方法
        /// <summary>
        /// 将数据写入到Socket，等待答复，并作简单判断
        /// </summary>
        private bool WriteSyncData(byte[] write_data, ushort id, ref byte[] data)
        {
            if (tcpSynCl.Connected)
            {
                try
                {
                    tcpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None);
                    int result = tcpSynCl.Receive(tcpSynClBuffer, 0, tcpSynClBuffer.Length, SocketFlags.None);

                    byte unit = tcpSynClBuffer[6];
                    byte function = tcpSynClBuffer[7];

                    if (result == 0)
                    {
                        LogPLC.L_I.WritePLC("SocketSendError", "接收到数据字节数为0，通讯断开");
                        return false;
                    }
                    // ------------------------------------------------------------
                    // Response data is slave exception
                    if (function > excExceptionOffset)
                    {
                        //function -= excExceptionOffset;
                        //CallException(id, unit, function, tcpSynClBuffer[8]);
                        LogPLC.L_I.WritePLC("SocketSendError", "数据反馈异常，异常码：" + tcpSynClBuffer[8]);
                        return false;
                    }
                    // ------------------------------------------------------------
                    // Write response data
                    else if ((function >= fctWriteSingleCoil) && (function != fctReadWriteMultipleRegister))
                    {
                        data = new byte[2];
                        Array.Copy(tcpSynClBuffer, 10, data, 0, 2);
                    }
                    // ------------------------------------------------------------
                    // Read response data
                    else
                    {
                        data = new byte[tcpSynClBuffer[8]];
                        Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]);
                    }
                    return true; ;
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {

            }
            return false;
        }

        private int ReadStartAdr(string Adress)
        {
            string regex = @"[^0-9]+";
            string str = Regex.Replace(Adress, regex, "");
            return Convert.ToInt32(str);
        }
        /// <summary>
        /// 将输入数组的i位和i+1位交换，得到一个新的数组
        /// </summary>
        /// <param name="rawData"></param>
        /// <exception cref="输入数组的长度为奇数" />
        /// <returns></returns>
        private byte[] ConvertBetweenPanData(byte[] rawData)
        {
            if (rawData.Length % 2 > 0)
            {
                throw new Exception("数组数据数量异常！");
            }

            int num = rawData.Length;
            byte[] byteConverted = new byte[num];
            for (int i = 0; i < num / 2; i++)
            {
                byteConverted[2 * i] = rawData[2 * i + 1];
                byteConverted[2 * i + 1] = rawData[2 * i];
            }
            return byteConverted;
        } 
        #endregion

    }
}
